﻿namespace WLFramework.Web.Binder
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WLFramework.Reflection.Metadata;
    using WLFramework.Validation;
    using WLFramework.Web.Binder.ValueProvider;
    
    public class WebModelBinder : IModelBinder
    {
        #region BindModel
        public ModelExceptionCollection BindModel<TModel>(
            HttpContext context,
            ModelMetadata metadata,
            out TModel model)
        {
            Shield.AssertNotNull(ref context, "context");
            Shield.AssertNotNull(ref metadata, "metadata");

            model = (TModel)metadata.Model;

            Dictionary<string, ModelState> states;

            GetValues(ref context, ref metadata, out states);
            SetValues(ref model, ref metadata, states);
            CheckValues(ref model, ref metadata, states);

            ModelExceptionCollection errors = new ModelExceptionCollection();

            foreach (var item in states)
            {
                var ex = item.Value.Errors.FirstOrDefault();
                if (ex != null)
                {
                    errors[item.Key] = ex;
                }
            }

            return errors;
        }

#if Net40
        public ModelExceptionCollection BindModel<TModel>(
            HttpContextBase context,
            ModelMetadata metadata,
            out TModel model)
        {
            Shield.AssertNotNull(ref context, "context");
            Shield.AssertNotNull(ref metadata, "metadata");

            model = (TModel)metadata.Model;

            Dictionary<string, ModelState> states;

            GetValues(ref context, ref metadata, out states);
            SetValues(ref model, ref metadata, states);
            CheckValues(ref model, ref metadata, states);

            ModelExceptionCollection errors = new ModelExceptionCollection();

            foreach (var item in states)
            {
                var ex = item.Value.Errors.FirstOrDefault();
                if (ex != null)
                {
                    errors[item.Key] = ex;
                }
            }

            return errors;
        }
#endif

        #endregion

        #region GetValues
        private static void GetValues(
            ref HttpContext context,
            ref ModelMetadata metadata,
            out Dictionary<string, ModelState> states)
        {
            states = new Dictionary<string, ModelState>();

            var providers = DefaultValueProviderFactory.GetDefaultProviders(context);
            var query = metadata.Members.Where(k => k.Ignore == null || !k.Ignore.IsIgnoreSet);
            foreach (var prop in query)
            {
                var state = new ModelState() { MemberInfo = prop };
                states.Add(prop.MemberName, state);

                // 搜索用户输入的值
                foreach (var provider in providers)
                {
                    var value = provider.GetValue(prop.FormName);
                    if (value != null)
                    {
                        state.Value = value;
                        break;
                    }
                }
            }
        }

#if Net40
        private static void GetValues(
            ref HttpContextBase context,
            ref ModelMetadata metadata,
            out Dictionary<string, ModelState> states)
        {
            states = new Dictionary<string, ModelState>();

            var providers = DefaultValueProviderFactory.GetDefaultProviders(context);
            var query = metadata.Members.Where(k => k.Ignore == null || !k.Ignore.IsIgnoreSet);
            foreach (var prop in query)
            {
                var state = new ModelState() { MemberInfo = prop };
                states.Add(prop.MemberName, state);

                // 搜索用户输入的值
                foreach (var provider in providers)
                {
                    var value = provider.GetValue(prop.MemberName);
                    if (value != null)
                    {
                        state.Value = value;
                        break;
                    }
                }
            }
        }
#endif
        #endregion

        private static void SetValues<TModel>(
            ref TModel model,
            ref ModelMetadata metadata,
            Dictionary<string, ModelState> states)
        {
            foreach (var item in states)
            {
                var state = item.Value;
                ModelMemberInfo prop = item.Value.MemberInfo;

                try
                {
                    if (state.Value != null &&
                        state.Value.AnyOneIsNull())
                    {
                        state.Value = null;
                    }

                    object targetValue = state.Value == null ?
                                         null :
                                         state.Value.ConvertTo(prop.Type);

                    if (targetValue != null)
                    {
                        state.MemberInfo.Setter.Setter(model, targetValue);
                    }
                }
                catch (Exception ex)
                {
                    // 异常原因可能是类型转换失败
                    string msg = Configuration.DebugMode ? ex.Message : string.Empty;
                    state.Errors.Add(new ModelException(prop, msg, metadata, ex));
                    throw;
                }
            }
        }

        private static void CheckValues<TModel>(
            ref TModel model,
            ref ModelMetadata metadata,
            Dictionary<string, ModelState> states)
        {
            foreach (var item in states)
            {
                var state = item.Value;
                var member = item.Value.MemberInfo;
                var context = new ValidateContext(metadata, member);

                // 目标字段不允许为空
                if (member.Required != null && state.Value == null)
                {
                    var result = member.Required.IsValid(null, context);
                    state.Errors.Add(CreateException(metadata, member, result.ErrorMessage));
                }                
                
                if(state.Value == null)
                {
                    continue;
                }

                foreach (var attr in member.Validations)
                {
                    var result = attr.IsValid(member.Getter.Getter(model), context);
                    if (result != ValidateResult.Success)
                    {
                        // 验证失败
                        state.Errors.Add(CreateException(metadata, member, result.ErrorMessage));
                        break;
                    }
                }
            }
        }

        private static ModelException CreateException(
            ModelMetadata metadata,
            ModelMemberInfo prop,
            string error)
        {
            return new ModelException(prop, error, metadata);
        }
    }
}
